/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xfd.learn.demos.oss;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * This demo code show you how to use oss in your application. You can use is directly or
 * refactor is to implement your own logic|architecture.
 *
 * @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
 */
@Service
public class OssDemoService {

    @Autowired
    private OSS ossClient;

//    /**
//     * 追加上传文件
//     *
//     * @param next 位置
//     * @param objectKey 对象键（文件名）
//     * @param inputStream inputStream
//     * @return 追加上传的结果
//     */
//    public AppendObjectResult appendFile(Long next, String objectKey, InputStream inputStream) {
//
//        try {
//            ObjectMetadata meta = new ObjectMetadata();
//            // 指定上传的内容类型。
//            meta.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//            // 指定该Object的网页缓存行为。
//            //meta.setCacheControl("no-cache");
//            // 指定该Object被下载时的名称。
//            //meta.setContentDisposition("attachment;filename=oss_download.txt");
//            // 指定该Object的内容编码格式。
//            //meta.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
//            // 该请求头用于检查消息内容是否与发送时一致。
//            //meta.setContentMD5("ohhnqLBJFiKkPSBO1eNaUA==");
//            // 指定过期时间。
//            //try {
//            //    meta.setExpirationTime(DateUtil.parseRfc822Date("Wed, 08 Jul 2022 16:57:01 GMT"));
//            //} catch (ParseException e) {
//            //    e.printStackTrace();
//            //}
//            // 指定服务器端加密方式。此处指定为OSS完全托管密钥进行加密（SSE-OSS）。
//            //meta.setServerSideEncryption(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
//            // 指定Object的访问权限。此处指定为私有访问权限。
//            //meta.setObjectAcl(CannedAccessControlList.Private);
//            // 指定Object的存储类型。
//            //meta.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
//            // 创建AppendObject时可以添加x-oss-meta-*，继续追加时不可以携带此参数。如果配置以x-oss-meta-*为前缀的参数，则该参数视为元数据。
//            //meta.setHeader("x-oss-meta-author", "Alice");
//
//            // 通过AppendObjectRequest设置多个参数。
//            AppendObjectRequest appendObjectRequest = new AppendObjectRequest("plan-oss-test", objectKey, new ByteArrayInputStream(inputStream.readAllBytes()),meta);
//
//            // 通过AppendObjectRequest设置单个参数。
//            // 设置Bucket名称。
//            //appendObjectRequest.setBucketName(bucketName);
//            // 设置Object名称。
//            //appendObjectRequest.setKey(objectName);
//            // 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为InputStream类型。
//            //appendObjectRequest.setInputStream(new ByteArrayInputStream(content1.getBytes()));
//            // 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为File类型。
//            //appendObjectRequest.setFile(new File("D:\\localpath\\examplefile.txt"));
//            // 指定文件的元数据，第一次追加时有效。
//            //appendObjectRequest.setMetadata(meta);
//
//            // 第一次追加。
//            // 设置文件的追加位置。
//            appendObjectRequest.setPosition(next);
//            AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
//            // 文件的64位CRC值。此值根据ECMA-182标准计算得出。
//            return appendObjectResult;
//
//            // 第二次追加。
//            // nextPosition表示下一次请求中应当提供的Position，即文件当前的长度。
////            appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
////            appendObjectRequest.setInputStream(new ByteArrayInputStream(content2.getBytes()));
////            appendObjectResult = ossClient.appendObject(appendObjectRequest);
//
//            // 第三次追加。
////            appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
////            appendObjectRequest.setInputStream(new ByteArrayInputStream(content3.getBytes()));
////            appendObjectResult = ossClient.appendObject(appendObjectRequest);
//        } catch (OSSException oe) {
//            System.out.println("Caught an OSSException, which means your request made it to OSS, "
//                    + "but was rejected with an error response for some reason.");
//            System.out.println("Error Message:" + oe.getErrorMessage());
//            System.out.println("Error Code:" + oe.getErrorCode());
//            System.out.println("Request ID:" + oe.getRequestId());
//            System.out.println("Host ID:" + oe.getHostId());
//        } catch (Exception e) {
//            System.out.println(e.getMessage());
//            e.printStackTrace();
//        }
//        return null;
//    }

//    public AppendObjectResult appendFile(Long next, String file, List<User> users) {
//        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
//
//            for (User user : users) {
//
//            }
//            EasyExcel.write(out, User.class).sheet("用户列表").doWrite(users);
//            ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
//            AppendObjectRequest appendObjectRequest = new AppendObjectRequest("plan-oss-test", "text.xlsx", in);
//            appendObjectRequest.setPosition(next);
//            return ossClient.appendObject(appendObjectRequest);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//    }
//
//    public AppendObjectResult appendFile1(Long next, String file, List<User> users) {
//        System.out.println("baos.toByteArray().length");
//        WriteSheet writeSheet = EasyExcel.writerSheet(1, "a").build();
//        AppendObjectResult appendObjectResult = null;
//        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
//
//            for (int i = 0; i < users.size(); i++) {
//                System.out.println(baos.toByteArray().length);
//                EasyExcel.write(baos, User.class).sheet(i+"").doWrite(Arrays.asList(users.get(i)));
//                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//                AppendObjectRequest appendObjectRequest = new AppendObjectRequest("plan-oss-test", "text.xlsx", bais);
//                appendObjectRequest.setPosition(next);
//                appendObjectResult = ossClient.appendObject(appendObjectRequest);
//                next = appendObjectResult.getNextPosition();
//                System.out.println("a"+baos.toByteArray().length);
//            }
//
//
//
//            return appendObjectResult;
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//    }



    public void InitiateMultipartUploadRequest() throws Exception{
        String objectName = "xyh-text.txt";
        String filePath = "C:\\Users\\xfd\\Desktop\\test.txt";
        String bucketName = "plan-oss-test";
        try {
            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);



            // 如果需要在初始化分片时设置请求头，请参考以下示例代码。
            ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // 指定该Object的网页缓存行为。
            // metadata.setCacheControl("no-cache");
            // 指定该Object被下载时的名称。
            // metadata.setContentDisposition("attachment;filename=oss_MultipartUpload.txt");
            // 指定该Object的内容编码格式。
            // metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
            // 指定初始化分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
            // metadata.setHeader("x-oss-forbid-overwrite", "true");
            // 指定上传该Object的每个part时使用的服务器端加密方式。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
            // 指定Object的加密算法。如果未指定此选项，表明Object使用AES256加密算法。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_DATA_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
            // 指定KMS托管的用户主密钥。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
            // 指定Object的存储类型。
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
            // 指定Object的对象标签，可同时设置多个标签。
            // metadata.setHeader(OSSHeaders.OSS_TAGGING, "a:1");
            // request.setObjectMetadata(metadata);

            // 根据文件自动设置ContentType。如果不设置，ContentType默认值为application/oct-srream。
            if (metadata.getContentType() == null) {
                metadata.setContentType(Mimetypes.getInstance().getMimetype(new File(filePath), objectName));
            }

            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId。
            String uploadId = upresult.getUploadId();
            // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
            // 如果您需要根据uploadId执行取消分片上传事件的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。
            // 如果您需要根据uploadId执行列举已上传分片的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后，且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
            // System.out.println(uploadId);

            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags =  new ArrayList<PartETag>();
            // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
            final long partSize = 1 * 1024 * 1024L;   //1 MB。

            // 根据上传的数据大小计算分片数。以本地文件为例，说明如何通过File.length()获取上传数据的大小。
            final File sampleFile = new File(filePath);
            long fileLength = sampleFile.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                // 设置上传的分片流。
                // 以本地文件为例说明如何创建FIleInputstream，并通过InputStream.skip()方法跳过指定数据。
                InputStream instream = new FileInputStream(sampleFile);
                instream.skip(startPos);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
                uploadPartRequest.setPartNumber( i + 1);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }

            for (PartETag partETag : partETags) {
                System.out.println(partETag.getETag());
                System.out.println(partETag.getPartNumber());
            }


            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。

            this.abortMultipartUpload(bucketName,objectName,uploadId);

            List<PartETag> partETagsList = this.getPartETagsList(bucketName, objectName, uploadId);
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETagsList);

            // 如果需要在完成分片上传的同时设置文件访问权限，请参考以下示例代码。
            // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
            // 指定是否列举当前UploadId已上传的所有Part。仅在Java SDK为3.14.0及以上版本时，支持通过服务端List分片数据来合并完整文件时，将CompleteMultipartUploadRequest中的partETags设置为null。
            // Map<String, String> headers = new HashMap<String, String>();
            // 如果指定了x-oss-complete-all:yes，则OSS会列举当前UploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
            // 如果指定了x-oss-complete-all:yes，则不允许继续指定body，否则报错。
            // headers.put("x-oss-complete-all","yes");
            // completeMultipartUploadRequest.setHeaders(headers);

            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            System.out.println(completeMultipartUploadResult.getETag());
        } catch (OSSException oe) {

            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    public List<PartETag> getPartETagsList(String bucketName , String objectName ,String uploadId){
        List<PartETag> list = new ArrayList<>();
        try {
            // 列举已上传的分片。
            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
            PartListing partListing = ossClient.listParts(listPartsRequest);

            for (PartSummary part : partListing.getParts()) {
                // 获取分片号。
                System.out.println(part.getPartNumber());
                // 获取分片数据大小。
                System.out.println(part.getSize());
                // 获取分片的ETag。
                System.out.println(part.getETag());
                // 获取分片的最后修改时间。
                System.out.println(part.getLastModified());
                PartETag partETag = new PartETag(part.getPartNumber(), part.getETag());
                list.add(partETag);
            }
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (Exception ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        }
        return list;
    }

    public void abortMultipartUpload(String bucketName , String objectName ,String uploadId){
        try {
            // 取消分片上传。
            AbortMultipartUploadRequest abortMultipartUploadRequest =
                    new AbortMultipartUploadRequest(bucketName, objectName, uploadId);
            ossClient.abortMultipartUpload(abortMultipartUploadRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (Exception ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        }
    }

}
